package eval

import (
	"showen/lex"
	"showen/obj"
	"showen/parse"
	"testing"
)

func TestEq(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected bool
	}{
		{"eq? #t with itself", "(eq? #t #t)", true},
		{"eq? #f with itself", "(eq? #f #f)", true},
		{"eq? #t with #f", "(eq? #t #f)", false},
		// According to R4RS, eq? tests for object identity
		// Numbers and strings that happen to have the same value are different objects
		{"eq? same integer", "(eq? 1 1)", false}, // Different objects in memory
		{"eq? different integers", "(eq? 1 2)", false},
		{"eq? same symbol", "(eq? 'a 'a)", true}, // Symbols with same name are the same object
		{"eq? different symbols", "(eq? 'a 'b)", false},
		{"eq? same string literals", "(eq? \"hello\" \"hello\")", false}, // Different objects in memory
		{"eq? different string literals", "(eq? \"hello\" \"world\")", false},
		{"eq? null with null", "(eq? '() '())", true}, // Empty lists are the same object (NULL_OBJECT)
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if result.Type() != obj.BOOLEAN_OBJ {
				t.Fatalf("Expected boolean result, got %v", result.Type())
			}

			boolResult := result.(*obj.BooleanObject)
			if boolResult.Value != tt.expected {
				t.Errorf("For input %q, expected %v, got %v", tt.input, tt.expected, boolResult.Value)
			}
		})
	}
}

func TestEqv(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected bool
	}{
		{"eqv? #t with itself", "(eqv? #t #t)", true},
		{"eqv? #f with itself", "(eqv? #f #f)", true},
		{"eqv? #t with #f", "(eqv? #t #f)", false},
		// According to R4RS, eqv? tests for value equality for simple types
		{"eqv? same integer", "(eqv? 1 1)", true}, // Same value
		{"eqv? different integers", "(eqv? 1 2)", false},
		{"eqv? same rational", "(eqv? 1/2 2/4)", true}, // Same value (simplified)
		{"eqv? different rationals", "(eqv? 1/2 1/3)", false},
		{"eqv? same character", "(eqv? #\\a #\\a)", true},
		{"eqv? different characters", "(eqv? #\\a #\\b)", false},
		{"eqv? same string", "(eqv? \"hello\" \"hello\")", true}, // Same value
		{"eqv? different strings", "(eqv? \"hello\" \"world\")", false},
		{"eqv? null with null", "(eqv? '() '())", true}, // Empty lists are the same object
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if result.Type() != obj.BOOLEAN_OBJ {
				t.Fatalf("Expected boolean result, got %v", result.Type())
			}

			boolResult := result.(*obj.BooleanObject)
			if boolResult.Value != tt.expected {
				t.Errorf("For input %q, expected %v, got %v", tt.input, tt.expected, boolResult.Value)
			}
		})
	}
}

func TestEqual(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name     string
		input    string
		expected bool
	}{
		// Boolean tests
		{"equal? true with itself", "(equal? #t #t)", true},
		{"equal? false with itself", "(equal? #f #f)", true},
		{"equal? true with false", "(equal? #t #f)", false},
		
		// Number tests
		{"equal? same integer", "(equal? 1 1)", true},
		{"equal? different integers", "(equal? 1 2)", false},
		{"equal? same rational", "(equal? 1/2 1/2)", true},
		{"equal? different rationals", "(equal? 1/2 1/3)", false},
		
		// Character tests
		{"equal? same character", "(equal? #\\a #\\a)", true},
		{"equal? different characters", "(equal? #\\a #\\b)", false},
		
		// String tests
		{"equal? same string", "(equal? \"hello\" \"hello\")", true},
		{"equal? different strings", "(equal? \"hello\" \"world\")", false},
		
		// List tests
		{"equal? empty lists", "(equal? '() '())", true},
		{"equal? same lists", "(equal? '(1 2 3) '(1 2 3))", true},
		{"equal? different lists", "(equal? '(1 2 3) '(1 2 4))", false},
		{"equal? nested lists", "(equal? '((1 2) (3 4)) '((1 2) (3 4)))", true},
		{"equal? different nested lists", "(equal? '((1 2) (3 4)) '((1 2) (3 5)))", false},
		
		// Null tests
		{"equal? null with null", "(equal? '() '())", true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			if result.Type() != obj.BOOLEAN_OBJ {
				t.Fatalf("Expected boolean result, got %v", result.Type())
			}

			boolResult := result.(*obj.BooleanObject)
			if boolResult.Value != tt.expected {
				t.Errorf("For input %q, expected %v, got %v", tt.input, tt.expected, boolResult.Value)
			}
		})
	}
}

func TestEquivalenceErrorHandling(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name     string
		input    string
		errorMsg string
	}{
		{"eq? with no arguments", "(eq?)", "eq? requires exactly 2 arguments"},
		{"eq? with one argument", "(eq? #t)", "eq? requires exactly 2 arguments"},
		{"eq? with three arguments", "(eq? #t #f #t)", "eq? requires exactly 2 arguments"},
		
		{"eqv? with no arguments", "(eqv?)", "eqv? requires exactly 2 arguments"},
		{"eqv? with one argument", "(eqv? #t)", "eqv? requires exactly 2 arguments"},
		{"eqv? with three arguments", "(eqv? #t #f #t)", "eqv? requires exactly 2 arguments"},
		
		{"equal? with no arguments", "(equal?)", "equal? requires exactly 2 arguments"},
		{"equal? with one argument", "(equal? #t)", "equal? requires exactly 2 arguments"},
		{"equal? with three arguments", "(equal? #t #f #t)", "equal? requires exactly 2 arguments"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("ParseExpression() error = %v", err)
			}

			result, err := EvalExpression(expr, env)
			if err != nil {
				// Evaluation error - this is what we expect
				if err.Error() != tt.errorMsg {
					t.Errorf("For input %q, expected error %q, got %q", tt.input, tt.errorMsg, err.Error())
				}
				return
			}

			// Check if the result is an error object
			if result.Type() != obj.ERROR_OBJ {
				t.Fatalf("Expected error result, got %v", result.Type())
			}

			errorResult := result.(*Error)
			if errorResult.Message != tt.errorMsg {
				t.Errorf("For input %q, expected error %q, got %q", tt.input, tt.errorMsg, errorResult.Message)
			}
		})
	}
}